package com.ybear.ybutils.utils

import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.ContentValues
import android.content.Context
import android.content.ContextWrapper
import android.content.DialogInterface
import android.content.DialogInterface.OnShowListener
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Resources
import android.content.res.Resources.NotFoundException
import android.graphics.Color
import android.graphics.Point
import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.os.IBinder
import android.provider.MediaStore
import android.provider.Settings
import android.telephony.TelephonyManager
import android.text.TextUtils
import android.util.TypedValue
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.view.Window
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.Space
import androidx.annotation.ColorInt
import androidx.annotation.RequiresApi
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.util.Consumer
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.ybear.ybutils.utils.handler.HandlerManage.Companion.create
import com.ybear.ybutils.utils.log.LogUtil
import java.io.BufferedInputStream
import java.io.BufferedReader
import java.io.File
import java.io.FileInputStream
import java.io.FileReader
import java.io.IOException
import java.io.OutputStream
import java.nio.charset.StandardCharsets
import java.util.UUID
import kotlin.math.ceil


/**
 * 系统工具类
 */
object SysUtil {
    @Retention(AnnotationRetention.SOURCE)
    annotation class StatusBarIconColor {
        companion object {
            @ColorInt
            var WHITE = Color.WHITE

            @ColorInt
            var BLACK = Color.BLACK
        }
    }

    interface OnWindowVisibleDisplayListener {
        fun onDisplay( rootInvisibleHeight: Int, isDisplay: Boolean )
    }

    private var mOnGlobalLayoutListener: OnGlobalLayoutListener? = null
    private val mOnWindowVisibleDisplayList: MutableList<OnWindowVisibleDisplayListener> = ArrayList()
    private val mWindowVisibleDisplayRect = Rect()
    private var mRootInvisibleHeight = 0

    /**
     * 解决部分机型 getObbDir() 闪退的问题
     */
    @JvmStatic
    fun getObbDir(context: Context?): File {
        var dir: File? = null
        val obbDirs = context?.obbDirs
        if( obbDirs?.isNotEmpty() == true ) dir = obbDirs[ obbDirs.size - 1 ]
        return ( dir ?: context?.obbDir ) as File
    }

    /**
     * 获取屏幕宽度
     * @param context   上下文
     * @return          宽度
     */
    @JvmOverloads
    @JvmStatic
    fun getScreenWidth(context: Context?, defaultWidth: Int = 0): Int {
        return context?.resources?.displayMetrics?.widthPixels ?: defaultWidth
    }

    /**
     * 获取屏幕高度
     * @param context   上下文
     * @return          高度
     */
    @JvmOverloads
    @JvmStatic
    fun getScreenHeight(context: Context?, defaultHeight: Int = 0): Int {
        return context?.resources?.displayMetrics?.heightPixels ?: defaultHeight
    }

    @JvmStatic
    fun getScreenTrueSize(context: Context?): Point {
        val point = Point( 0, 0 )
        // 如果 context 为 null，则返回默认尺寸 (0, 0)
        context ?: return point
        return if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ) {
            val windowManager = context.getSystemService( WindowManager::class.java )
            val metrics = windowManager?.currentWindowMetrics ?: return point
            val insets = metrics.windowInsets.getInsetsIgnoringVisibility(
                WindowInsets.Type.navigationBars() or WindowInsets.Type.statusBars()
            )
            point.set(
                metrics.bounds.width() - insets.left - insets.right,
                metrics.bounds.height() - insets.top - insets.bottom
            )
            point
        } else {
            val service = context.getSystemService( Context.WINDOW_SERVICE )
            val wm = ( service as? WindowManager ) ?: return point
            @Suppress("DEPRECATION")
            wm.defaultDisplay.getRealSize( point )
            point
        }
    }

    /**
     * 获取屏幕真实宽度
     * @param context   上下文
     * @return          真实宽度
     */
    @JvmStatic
    fun getScreenTrueWidth(context: Context): Int { return getScreenTrueSize( context ).x }

    /**
     * 获取屏幕真实高度
     * @param context   上下文
     * @return          真实高度
     */
    @JvmStatic
    fun getScreenTrueHeight(context: Context): Int { return getScreenTrueSize( context ).y }

    /**
     * 获取指定包名的信息
     * @param context           上下文
     * @param packageName       包名
     * @return                  包名信息
     */
    @JvmStatic
    fun getPackageInfo(context: Context?, packageName: String?): PackageInfo? {
        try {
            return packageName?.let { context?.packageManager?.getPackageInfo( it, 0 ) }
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
        return null
    }

    /**
     * 获取当前包的信息
     * @param context   上下文
     * @return          [PackageInfo]
     */
    @JvmStatic
    fun getPackageInfo(context: Context?): PackageInfo? {
        return if( context == null ) null else getPackageInfo( context, context.packageName )
    }

    @JvmStatic
    @SuppressLint("QueryPermissionsNeeded")
    fun getInstalledPackages(context: Context?): List<PackageInfo> {
        val retList: MutableList<PackageInfo> = ArrayList()
        if( context == null ) return retList
        try {
            val pm = context.packageManager
            if( pm != null ) retList.addAll( pm.getInstalledPackages( 0 ) )
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return retList
    }

    @Suppress("DEPRECATION")
    @JvmStatic
    fun getPreferredPackages(context: Context?): List<PackageInfo> {
        val retList: MutableList<PackageInfo> = ArrayList()
        if (context == null) return retList
        try {
            val pm = context.packageManager
            // getPreferredPackages 已过时，但为了兼容旧版逻辑仍可使用
            retList.addAll(pm.getPreferredPackages(0))
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return retList
    }


    /**
     * 检查包名是否已经安装
     * @param context               上下文
     * @param checkPackageName      检查的包名
     * @return                      是否安装
     */
    @JvmStatic
    fun isInstallApk(context: Context?, checkPackageName: String?): Boolean {
        if (context == null || checkPackageName == null) return false
        val list = getInstalledPackages( context )
        if ( list.isEmpty() ) return false
        for (pi in list) {
            if ( checkPackageName == pi.packageName ) return true
        }
        return false
    }

    /**
     * 沉浸式
     */
    @Suppress("DEPRECATION")
    @JvmOverloads
    @JvmStatic
    fun immersiveStatusBar(window: Window?,
                           enable: Boolean = true,
                           defColor: Int = android.R.color.background_dark) {
        window ?: return

        if (enable) {
            // 开启沉浸式状态栏
            // API 21 及以上均支持 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
            // 清除旧的半透明标志
            window.clearFlags(
                WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or
                        WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
            )
            // AndroidX 提供的兼容方法，可使内容延伸至状态栏区域
            WindowCompat.setDecorFitsSystemWindows(window, false)
            // 设置状态栏背景为透明
            window.statusBarColor = Color.TRANSPARENT
            // 针对 API 29 及以上，关闭状态栏对比度限制，避免图标显示异常
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                window.isStatusBarContrastEnforced = false
            }
        } else {
            // 关闭沉浸式状态栏，恢复默认效果
            window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
            window.addFlags(
                WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or
                        WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
            )
            WindowCompat.setDecorFitsSystemWindows(window, true)
            // 使用传入的默认颜色（通过 ContextCompat 获取颜色资源）
            window.statusBarColor = ContextCompat.getColor(window.context, defColor)
        }
    }


    /**
     * 将Activity设置为透明
     * super.onCreate(savedInstanceState);
     * //Call here
     * SysUtil.setTranslucentActivity( this, true );
     * setContentView(...);
     *
     * 如果调用了：[SysUtil.setStatusBarIconColor] 可能会失效
     */
    /**
     * 将Activity设置为透明
     * super.onCreate(savedInstanceState);
     * //Call here
     * SysUtil.setTranslucentActivity( this, true );
     * setContentView(...);
     *
     * 如果调用了：[SysUtil.setStatusBarIconColor] 可能会失效
     */
    @Suppress("DEPRECATION")
    @JvmStatic
    fun setTranslucentActivity(activity: Activity?, isTranslucent: Boolean) {
        if (activity == null) return
        val window = activity.window ?: return

        // 统一设置 Activity 的主题，确保风格一致
        setTheme(activity, R.style.translucentActivity)

        if (!isTranslucent) {
            window.setBackgroundDrawableResource(android.R.color.white)
            return
        }

        // 设置透明背景和无暗影效果
        window.setBackgroundDrawableResource(android.R.color.transparent)
        window.setDimAmount(0f)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            // API 30及以上：内容延伸到状态栏区域
            window.setDecorFitsSystemWindows(false)
        } else {
            // API 21 ~ API 29：使用 FLAG_TRANSLUCENT_STATUS 配合 systemUiVisibility
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
            val decorView = window.decorView
            decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
        }

        // 设置状态栏颜色为透明
        window.statusBarColor = Color.TRANSPARENT

        // 关闭状态栏对比度限制（仅 API 29 及以上有效）
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            window.isStatusBarContrastEnforced = false
        }

        // 隐藏系统 ActionBar（如果存在）
        activity.actionBar?.hide()
    }


    @JvmStatic
    fun setTheme(activity: Activity?, @StyleRes resId: Int) {
        if (activity == null) return
        val theme = activity.theme
        if (theme == null) {
            activity.setTheme( resId )
        } else {
            theme.applyStyle( resId, true )
        }
    }

    @Suppress("DEPRECATION")
    @JvmStatic
    fun setStatusBarIconColor(w: Window, @StatusBarIconColor color: Int) {
        val isBlack = color == StatusBarIconColor.BLACK
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            // API 30 及以上，使用 WindowInsetsController
            w.insetsController?.setSystemBarsAppearance(
                if (isBlack) WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS else 0,
                WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
            )
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // API 23 ~ API 29，使用系统的 systemUiVisibility 标志
            val decorView = w.decorView
            var vis = decorView.systemUiVisibility
            vis = if (isBlack) {
                vis or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
            } else {
                vis and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
            }
            decorView.systemUiVisibility = vis
        } else if (isBlack) {
            // API 21 ~ API 22（或更低），直接设置状态栏颜色（此时无法设置图标颜色，只能改变背景）
            w.statusBarColor = color
        }
    }


    /**
     * 获取系统层资源Id
     * @param name          获取的资源名称
     * @param defType       类型
     * @return              资源Id
     */
    @JvmStatic
    fun getSystemIdentifier(name: String?, defType: String?): Int {
        return Resources.getSystem().getIdentifier(name, defType, "android")
    }

    /**
     * 获取ActionBar的高度
     */
    @JvmStatic
    fun getActionBarHeight(context: Context) : Int {
        // 优先使用 AppCompatActivity 的 supportActionBar
        if ( context is AppCompatActivity) return context.supportActionBar?.height ?: 0
        // 如果不是 AppCompatActivity，尝试通过主题属性获取 ActionBar 高度
        val typeVal = TypedValue()
        val attr = android.R.attr.actionBarSize
        if( !context.theme.resolveAttribute( attr, typeVal, true ) ) return 0
        return TypedValue.complexToDimensionPixelSize(
            typeVal.data, context.resources.displayMetrics
        )
    }

    /**
     * 获取指定系统的资源id尺寸
     * @param name          获取的尺寸名称
     * @return              尺寸大小
     */
    @JvmStatic
    fun getSystemDimen(name: String?): Int {
        var result = 0
        try {
            val id = getSystemIdentifier(name, "dimen")
            if (id > 0) result = Resources.getSystem().getDimensionPixelSize(id)
        } catch (e: NotFoundException) {
            e.printStackTrace()
        }
        return result
    }

    /**
     * 获取状态栏高度
     * @return      高度
     */
    @JvmStatic
    val statusBarHeight: Int get() = getSystemDimen("status_bar_height")

    /**
     * 获取导航栏高度
     * @return      高度
     */
    @JvmStatic
    val navigationBarHeight: Int get() = getSystemDimen("navigation_bar_height")

    /**
     * 设置View的topPadding高度为状态栏高度
     * @param v         View
     * @param height    传入[Integer.MIN_VALUE]时，自动填充状态栏高度
     */
    @JvmStatic
    fun setStatusBarHeightForMargin(v: View?, height: Int) : Boolean {
        if ( v == null ) return false
        val top = if (height == Int.MIN_VALUE) statusBarHeight else height
        val lp = v.layoutParams
        if( lp !is MarginLayoutParams ) return setStatusBarHeightForPadding( v, height )
        lp.topMargin = top
        v.layoutParams = lp
        return true
    }

    /**
     * 设置View的topPadding高度为状态栏高度
     * @param v         View
     */
    @JvmStatic
    fun setStatusBarHeightForMargin(v: View?) : Boolean {
        return setStatusBarHeightForMargin( v, Int.MIN_VALUE )
    }

    /**
     * 设置View的topPadding高度为状态栏高度
     * @param v         View
     * @param height    传入[Integer.MIN_VALUE]时，自动填充状态栏高度
     */
    @JvmStatic
    fun setStatusBarHeightForPadding(v: View?, height: Int) : Boolean {
        if ( v == null ) return false
        if( v is Space ) {
            return setStatusBarHeightForSpace( v, height )
        }
        v.setPadding(
            v.paddingLeft,
            if (height == Int.MIN_VALUE) statusBarHeight else height,
            v.paddingRight,
            v.paddingBottom
        )
        return true
    }

    /**
     * 设置View的topPadding高度为状态栏高度
     * @param v     View
     */
    @JvmStatic
    fun setStatusBarHeightForPadding(v: View?) : Boolean {
        return setStatusBarHeightForPadding(v, Int.MIN_VALUE)
    }

    /**
     * 设置Space的高度为状态栏高度
     * @param s     Space
     * @param height    传入[Integer.MIN_VALUE]时，自动填充状态栏高度
     */
    @JvmOverloads
    @JvmStatic
    fun setStatusBarHeightForSpace(s: Space?, height: Int = Int.MIN_VALUE) : Boolean {
        if ( s == null ) return false
        var lp = s.layoutParams
        val h = if (height == Int.MIN_VALUE) statusBarHeight else height
        if ( lp == null ) {
            lp = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, h )
        } else {
            lp.height = h
        }
        s.layoutParams = lp
        return true
    }

    /**
     * 启用全屏
     * @param window     [Activity.getWindow]
     * @param enable     是否启用全屏
     */
    @JvmOverloads
    @JvmStatic
    fun setFullScreen(window: Window, enable: Boolean = true) {
        WindowCompat.setDecorFitsSystemWindows(window, !enable)
        val controller = WindowInsetsControllerCompat(window, window.decorView)
        if (enable) {
            controller.hide(WindowInsetsCompat.Type.systemBars())
            controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        } else {
            controller.show(WindowInsetsCompat.Type.systemBars())
        }
    }

    /**
     * 杀死指定包名的app
     * 需要权限：android.permission.KILL_BACKGROUND_PROCESSES
     * @param context       上下文
     * @param pageName      包名
     */
    @SuppressLint("MissingPermission")
    @JvmStatic
    fun killProcesses(context: Context?, pageName: String?) {
        if( TextUtils.isEmpty( pageName ) ) return
        ( context?.getSystemService( Context.ACTIVITY_SERVICE ) as ActivityManager )
            .killBackgroundProcesses( pageName )
    }

    @JvmStatic
    fun getInputMethodManager(context: Context?): InputMethodManager? {
        return if (context == null) null else context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    }

    /**
     * 显示/隐藏软键盘
     * @param activity      活动的页面
     * @param et            编辑框
     * @param isShow        是否显示/隐藏
     * @param call          显示/隐藏回调
     * @param <ET>          必须是编辑框
    </ET> */
    @JvmStatic
    fun <ET : EditText?> showKeyboard(activity: Activity?,
                                      et: ET?,
                                      isShow: Boolean = false,
                                      call: Consumer<Boolean?>? = null) {
        if (activity == null) return
        val imm = activity.getSystemService( Context.INPUT_METHOD_SERVICE ) as InputMethodManager
        val focusView = et ?: activity.currentFocus
        val r = Runnable {
            if (isShow) {
                val showResult: Boolean = if ( focusView == null ) {
                    @Suppress("DEPRECATION")
                    imm.toggleSoftInput( InputMethodManager.SHOW_FORCED, 0 )
                    true
                } else {
                    imm.showSoftInput( focusView, InputMethodManager.SHOW_FORCED )
                }
                call?.accept( showResult )
                return@Runnable
            }
            var ib: IBinder? = null
            if (focusView == null) {
                val curFocusView = activity.currentFocus
                if (curFocusView != null) ib = curFocusView.windowToken
            } else {
                ib = focusView.windowToken
            }
            if (ib == null || !imm.isActive) return@Runnable
            val showResult = imm.hideSoftInputFromWindow( ib, 0 )
            call?.accept(showResult)
        }
        if ( focusView == null ) {
            create().post(r)
        } else {
            focusView.isFocusable = true
            focusView.isFocusableInTouchMode = true
            focusView.requestFocus()
            focusView.post(r)
        }
    }

    /**
     * 显示/隐藏软键盘
     * @param activity      活动的页面
     * @param et            编辑框
     * @param isShow        是否显示/隐藏
     * @param <ET>          必须是编辑框
    </ET> */
    @JvmStatic
    fun <ET : EditText?> showKeyboard(activity: Activity?, et: ET?, isShow: Boolean) {
        showKeyboard<ET?>(activity, et, isShow, null)
    }

    /**
     * 显示/隐藏软键盘
     * @param activity      活动的页面
     * @param isShow        是否显示/隐藏
     */
    @JvmStatic
    fun showKeyboard(activity: Activity?, isShow: Boolean) {
        showKeyboard<EditText?>(activity, null, isShow)
    }

    /**
     * 显示软键盘
     * @param et    需要显示的编辑框已显示
     */
    @JvmStatic
    fun <ET : EditText?> showKeyboard(activity: Activity?, et: ET, call: Consumer<Boolean?>?) {
        showKeyboard<ET>(activity, et, true, call)
    }

    /**
     * 关闭软键盘
     * @param activity  上下文
     */
    @JvmStatic
    fun <ET : EditText?> hideKeyboard(activity: Activity?, et: ET, call: Consumer<Boolean?>?) {
        showKeyboard<ET>(activity, et, false, call)
    }

    /**
     * 显示软键盘
     * @param activity  上下文
     */
    @JvmStatic
    @JvmOverloads
    fun <ET : EditText?> showKeyboard(activity: Activity?, et: ET? = null) {
        showKeyboard(activity, et, null)
    }

    /**
     * 关闭软键盘
     * @param activity  上下文
     */
    @JvmStatic
    @JvmOverloads
    fun <ET : EditText?> hideKeyboard(activity: Activity?, et: ET? = null) {
        hideKeyboard( activity, et, null )
    }

    private val keyboardStatusRect = Rect()

    /**
     * 监听键盘弹出/关闭状态
     */
    @JvmStatic
    @JvmOverloads
    fun setOnKeyboardStatusListener(view: View,
                                    offset: Float = 0.15F,
                                    keepListener: Boolean = true,
                                    call: Consumer<Boolean>) {
        val vto = view.viewTreeObserver ?: return
        val onGlobal = object : OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                view.getWindowVisibleDisplayFrame( keyboardStatusRect )
                val h = view.rootView?.height ?: 0
                // 判断键盘是否弹出
                call.accept( h - keyboardStatusRect.bottom > h * offset )
                // 只监听一次
                if( !keepListener ) vto.removeOnGlobalLayoutListener( this )
            }
        }
        vto.addOnGlobalLayoutListener( onGlobal )
    }

    /**
     * 当前软键盘是否被打开
     */
    fun isShowKeyboard(activity: Activity): Boolean {
        // 虚拟键盘隐藏 判断view是否为空
        activity.window.peekDecorView() ?: return false
        // 隐藏虚拟键盘
        return (
                activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
                ).isActive
                && activity.window.currentFocus != null
    }

    private var mAutoHideKeyboardTime: Long = 0
    /**
     * 自动隐藏键盘并且清除当前焦点
     * @param activity  活动页
     * @param ev        [Activity.dispatchTouchEvent]
     * [Activity.onTouchEvent]
     * @param xy        用于记录坐标，可以定义为常量
     */
    @JvmStatic
    fun autoHideKeyboardAndClearFocus(activity: Activity?, ev: MotionEvent, xy: IntArray) {
        if (activity == null) return
        val action = ev.action
        if (action == MotionEvent.ACTION_DOWN) mAutoHideKeyboardTime = System.currentTimeMillis()
        if (action == MotionEvent.ACTION_UP) {
            if (System.currentTimeMillis() - mAutoHideKeyboardTime > 150) return
        } else {
            return
        }
        val focusView = activity.currentFocus ?: return
        focusView.getLocationOnScreen(xy)
        val evX = ev.x
        val evY = ev.y
        val fvX1 = xy[0]
        val fvY1 = xy[1]
        val fvX2 = fvX1 + focusView.width
        val fvY2 = fvY1 + focusView.height
        if (evX < fvX1 || evX > fvX2 || evY < fvY1 || evY > fvY2) {
            hideKeyboard( activity, null )
            focusView.clearFocus()
        }
    }

    /**
     * dialog自动弹出键盘
     * @param context   上下文
     * @param d         Dialog
     * @param et        Dialog下的编辑框
     */
    @JvmStatic
    @JvmOverloads
    fun autoPopUpKeyboard(context: Context, et: EditText, d: Dialog, l: OnShowListener? = null) {
        d.setOnShowListener { dialog: DialogInterface? ->
            val imm = getInputMethodManager( context )
            imm?.showSoftInput( et, InputMethodManager.SHOW_IMPLICIT)
            l?.onShow(dialog)
        }
    }

    /**
     * 复制到剪贴板
     * @param context     上下文
     * @param label       用户可见的标签
     * @param content     内容
     * @return            是否复制成功
     */
    @JvmStatic
    fun copyTextToClipboard(context: Context?, label: String?, content: String?): Boolean {
        if (context == null) return false
        val cm = context.getSystemService( Activity.CLIPBOARD_SERVICE ) as ClipboardManager
        val data = ClipData.newPlainText( label, content )
        try {
            cm.setPrimaryClip( data )
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    @JvmStatic
    fun copyTextToClipboard(context: Context?, content: String?): Boolean {
        return copyTextToClipboard( context, "text", content )
    }

    /**
     * 从剪贴板中粘贴文本
     * @param context     上下文
     * @param isTopData   是否只获取第一条文本
     * @return            剪贴数据
     */
    @JvmStatic
    fun pasteTextFromClipboards(context: Context?, isTopData: Boolean): Array<ClipData.Item?>? {
        if ( context == null ) return null
        val cm = context.getSystemService( Activity.CLIPBOARD_SERVICE ) as ClipboardManager
        val data = cm.primaryClip
        if( data == null || data.itemCount == 0 ) return null
        //只返回剪贴板第一条数据
        if( isTopData ) return arrayOf( data.getItemAt( 0 ) )
        val items = arrayOfNulls<ClipData.Item>( data.itemCount )
        for( i in items.indices ) items[ i ] = data.getItemAt( i )
        return items
    }

    /**
     * 从剪贴板中粘贴文本 - 高版本兼容
     * @param context     上下文
     * @param isTopData   是否只获取第一条文本
     * @param call        剪贴数据
     */
    @JvmStatic
    fun pasteTextFromClipboards(context: Context?, isTopData: Boolean,
                                call: Consumer<Array<ClipData.Item?>?>?) {
        create().post({ call?.accept(
            if( context == null ) null else pasteTextFromClipboards( context, isTopData )
        )}, 1000)
    }

    /**
     * 从剪贴板中粘贴文本 - 高版本兼容
     * @param context     上下文
     * @param call        剪贴数据
     */
    @JvmStatic
    fun pasteTextFromClipboards(context: Context?, call: Consumer<Array<ClipData.Item?>?>?) {
        create().post({ call?.accept(
            if( context == null ) null else pasteTextFromClipboards( context, false )
        )}, 1000)
    }

    /**
     * 从剪贴板中粘贴文本
     * @param context     上下文
     * @return            剪贴数据
     */
    @JvmStatic
    fun pasteTextFromClipboard(context: Context?): ClipData.Item? {
        if( context == null ) return null
        val items = pasteTextFromClipboards(context, true)
        return if( items?.isNotEmpty() == true ) items[ 0 ] else null
    }

    /**
     * 从剪贴板中粘贴文本 - 高版本兼容
     * @param context     上下文
     * @param call        剪贴数据
     */
    @JvmStatic
    fun pasteTextFromClipboard(context: Context?, call: Consumer<ClipData.Item?>?) {
        create().post({
            call?.accept( if( context == null ) null else pasteTextFromClipboard( context ) )
        }, 1000)
    }

    /**
     * 从剪贴板中粘贴文本
     * @param context     上下文
     * @return            剪贴数据
     */
    @JvmStatic
    fun pasteTextFromClipboardOfString(context: Context?): String? {
        if( context == null ) return null
        return pasteTextFromClipboard( context )?.text?.toString()
    }

    /**
     * 从剪贴板中粘贴文本 - 高版本兼容
     * @param context     上下文
     * @param call        剪贴数据
     */
    @JvmStatic
    fun pasteTextFromClipboardOfString(context: Context?, call: Consumer<String?>?) {
        create().post({
            call?.accept(
                if( context == null ) null else pasteTextFromClipboardOfString( context )
            )
        }, 1000)
    }

    /**
     * 添加对系统屏幕显示高度的监听
     * @param w           [Activity.getWindow]
     * @param l           监听器
     */
    @JvmStatic
    fun addOnWindowVisibleDisplayListener(w: Window?, l: OnWindowVisibleDisplayListener) {
        w?.apply {
            mOnWindowVisibleDisplayList.add( l )
            mOnGlobalLayoutListener = OnGlobalLayoutListener {
                decorView.getWindowVisibleDisplayFrame( mWindowVisibleDisplayRect )
                val h = decorView.height - mWindowVisibleDisplayRect.bottom
                if( mRootInvisibleHeight != h ) {
                    l.onDisplay( mRootInvisibleHeight, h >= mRootInvisibleHeight )
                }
                mRootInvisibleHeight = h
            }
            decorView.viewTreeObserver.addOnGlobalLayoutListener( mOnGlobalLayoutListener )
        }
    }

    /**
     * 移除对系统屏幕显示高度的监听
     * @param w           [Activity.getWindow]
     * @param l           监听器
     */
    @JvmStatic
    fun removeOnWindowVisibleDisplayListener(w: Window?, l: OnWindowVisibleDisplayListener) {
        w?.apply {
            mOnWindowVisibleDisplayList.remove( l )
            if( mOnWindowVisibleDisplayList.size > 0 ) return
            decorView.viewTreeObserver?.removeOnGlobalLayoutListener( mOnGlobalLayoutListener )
        }
    }

    /**
     * 安装Apk安装包
     * @param context         上下文
     * @param filePath        文件路径
     * @return                安装结果
     */
    @JvmStatic
    fun installApk(context: Context?, filePath: String?): Boolean {
        if ( context == null || TextUtils.isEmpty( filePath ) ) return false
        try {
            val intent = Intent( Intent.ACTION_VIEW )
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
            if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ) {
                //增加读写权限
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            }
            intent.setDataAndType(
                filePathToUri(context, filePath), "application/vnd.android.package-archive"
            )
            context.startActivity(intent)
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    /**
     * 通过文件路径获取Uri
     * @param context         上下文
     * @param filePath        文件路径
     * @return                文件Uri
     */
    private fun filePathToUri(context: Context?, filePath: String?): Uri? {
        if (context == null || TextUtils.isEmpty(filePath)) return null
        val f = filePath?.let { File( it ) }
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            f?.let {
                FileProvider.getUriForFile(
                    context, context.packageName + ".fileProvider", it
                )
            }
        } else {
            Uri.fromFile( f )
        }
    }

    /**
     * 获取Apk文件信息
     * @param context         上下文
     * @param apkPath         apk文件路径
     * @return                包信息
     */
    @JvmStatic
    fun getApkOfPackageInfo(context: Context?, apkPath: String?): PackageInfo? {
        if ( context == null || apkPath == null ) return null
        val pm = context.packageManager ?: return null
        val info = pm.getPackageArchiveInfo( apkPath, PackageManager.GET_ACTIVITIES )
            ?: return null
        val appInfo = info.applicationInfo ?: return null
        try {
            appInfo.sourceDir = apkPath
            appInfo.publicSourceDir = apkPath
            return info
        } catch (e: OutOfMemoryError) {
            e.printStackTrace()
        }
        return null
    }

    @SuppressLint("HardwareIds", "MissingPermission")
    fun getDeviceId(context: Context): String {
        var uuid: UUID? = null
        val keyDeviceId = "device_id"
        synchronized( SysUtil::class.java ) {
            val prefs = context.getSharedPreferences("device_id.xml", 0)
            val id = prefs.getString("device_id", null)
            if (TextUtils.isEmpty(id)) {
                val androidId: String
                val deviceId: String?
                var idByte: ByteArray? = null
                try {
                    androidId = Settings.Secure.getString(
                        context.contentResolver, Settings.Secure.ANDROID_ID
                    )
                    if ("9774d56d682e549c" == androidId) {
                        //android.permission.READ_PRIVILEGED_PHONE_STATE
                        @Suppress("DEPRECATION")
                        deviceId = ( context.getSystemService(
                            Context.TELEPHONY_SERVICE
                        ) as TelephonyManager ).deviceId
                        if (deviceId != null) {
                            idByte = deviceId.toByteArray( StandardCharsets.UTF_8 )
                        }
                        uuid = if( deviceId != null ) UUID.nameUUIDFromBytes( idByte ) else UUID.randomUUID()
                    } else {
                        idByte = androidId.toByteArray( StandardCharsets.UTF_8 )
                        uuid = UUID.nameUUIDFromBytes( idByte )
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                }
                if (uuid != null) {
                    prefs.edit().putString(keyDeviceId, uuid.toString()).apply()
                }
            } else {
                uuid = UUID.fromString(id)
            }
        }
        return if (uuid == null) "" else uuid.toString()
    }

    private var mMemoryInfo: ActivityManager.MemoryInfo? = null
    /**
     * 获取内存信息
     * @param context       上下文
     * @return              内存信息
     */
    @JvmStatic
    fun getMemoryInfo(context: Context?): ActivityManager.MemoryInfo? {
        if (context == null) return null
        try {
            if ( mMemoryInfo == null ) mMemoryInfo = ActivityManager.MemoryInfo()
            (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager)
                .getMemoryInfo( mMemoryInfo )
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return mMemoryInfo
    }

    /**
     * 获取当前可用内存大小（单位：B）
     * @param context       上下文
     * @return              1234567890
     */
    @JvmStatic
    fun getAvailMemory(context: Context?): Long {
        return getMemoryInfo( context )?.availMem ?: 0L
    }

    /**
     * 获取当前可用内存大小（浮点，单位：自动计算，以4G内存为例）
     * @param context       上下文
     * @return              eg:1.2
     */
    @JvmStatic
    fun getAvailMemoryOfDouble(context: Context?): Double {
        return ObjUtils.toMemorySizeOfDouble( context, getAvailMemory( context ).toDouble())
    }

    /**
     * 获取当前可用内存大小（浮点，单位：自动计算，以4G内存为例）
     * @param context       上下文
     * @return              eg:1.2
     */
    @JvmStatic
    fun getAvailMemoryOfCeil(context: Context?): Double {
        return ceil( getAvailMemoryOfDouble( context ) )
    }

    /**
     * 获取当前总内存大小（单位：B）
     * @param context       上下文
     * @return              1234567890, 单位：B
     */
    @JvmStatic
    fun getTotalMemory(context: Context?): Long {
        return getMemoryInfo( context )?.totalMem ?: 0L
    }

    /**
     * 获取当前可用内存大小（浮点，单位：自动计算，以4G内存为例）
     * @param context       上下文
     * @return              eg:3.8
     */
    @JvmStatic
    fun getTotalMemoryOfDouble(context: Context?): Double {
        return ObjUtils.toMemorySizeOfDouble( context, getTotalMemory( context ).toDouble() )
    }

    /**
     * 获取当前可用内存大小（浮点，单位：自动计算，以4G内存为例）
     * @param context       上下文
     * @return              eg:4
     */
    @JvmStatic
    fun getTotalMemoryOfCeil(context: Context?): Double {
        //获取的内存单位为kb，需要将kb转为b//第一行为总内存大小// 系统内存信息文件
        return ceil( getTotalMemoryOfDouble( context ) )
    }

    /**
     * 获取总内存大小（低版本适配）
     */
    @JvmStatic
    fun getTotalMemoryByLowVersion(): Long {
        val path = "/proc/meminfo" // 系统内存信息文件
        val readLine: String
        var arr: Array<String?>? = null
        val memory: Long
        var reader: BufferedReader? = null
        try {
            reader = BufferedReader(FileReader(path), 8192)
            //第一行为总内存大小
            readLine = reader.readLine()
            arr = if( TextUtils.isEmpty( readLine ) ) {
                null
            } else {
                readLine.split( "\\s+".toRegex() ).dropLastWhile { it.isEmpty() }.toTypedArray()
            }
        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            //获取的内存单位为kb，需要将kb转为b
            memory = if( arr != null && arr.size > 2 ) {
                ObjUtils.parseLong( arr[ 1 ] ) * 1024
            } else
                0
            try {
                reader?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
        return memory
    }

    @JvmStatic
    fun refreshSystemPicture(context: Context?, f: File?): Boolean {
        if( context == null || f == null ) return false
        if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ) return insertPicInAndroidQ( context, f )
        val cr = context.contentResolver
        val path = f.absolutePath
        if (cr != null && !TextUtils.isEmpty(path)) {
            try {
                val cv = ContentValues()
                val suffix = path.substring( path.lastIndexOf( "." ) + 1 )
                cv.put( MediaStore.Images.Media.MIME_TYPE, "image/$suffix" )
                cv.put( MediaStore.Images.Media.DATA, path )
                cr.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cv )
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        var contentUri: Uri? = null
        if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ) {
            try {
                contentUri = FileProvider.getUriForFile(
                    context, "{applicationId}.fileProvider", f
                )
            } catch (e: Exception) {
                e.printStackTrace()
                try {
                    val appInfo = context.applicationInfo
                        ?: throw NullPointerException("not find applicationInfo.")
                    contentUri = FileProvider.getUriForFile(
                        context, appInfo.processName + ".fileProvider", f
                    )
                } catch (e1: Exception) {
                    e.printStackTrace()
                }
            }
        } else {
            try {
                contentUri = Uri.fromFile( File( f.path ) )
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        if( contentUri == null ) return false
        @Suppress("DEPRECATION")
        context.sendBroadcast(Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, contentUri ))
        return true
    }

    @RequiresApi(Build.VERSION_CODES.Q)
    private fun insertPicInAndroidQ(context: Context?, f: File): Boolean {
        val values = ContentValues()
        val name = f.name
        val path = f.absolutePath
        val lastIndex = path.lastIndexOf(".")
        val suffix = if (lastIndex == -1) "jpg" else path.substring(lastIndex + 1)
        values.put( MediaStore.Images.Media.DESCRIPTION, name )
        values.put( MediaStore.Images.Media.DISPLAY_NAME, name )
        values.put( MediaStore.Images.Media.MIME_TYPE, "image/$suffix" )
        values.put( MediaStore.Images.Media.TITLE, "Image.$suffix" )
        values.put( MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" )
        val external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        val resolver = context?.contentResolver
        val insertUri = resolver?.insert( external, values )
        var os: OutputStream? = null
        var bis: BufferedInputStream? = null
        return try {
            bis = BufferedInputStream( FileInputStream( f ) )
            os = insertUri?.let { resolver.openOutputStream( it ) }
            val bs = ByteArray( 4096 )
            var len: Int
            while( bis.read( bs ).also { len = it } != -1) os?.write( bs, 0, len )
            os?.flush()
            true
        } catch (e: IOException) {
            e.printStackTrace()
            false
        } finally {
            try { os?.close() } catch (ignored: IOException) { }
            try { bis?.close() } catch (ignored: IOException) { }
        }
    }

    @JvmStatic
    fun generateViewId(): Int { return ViewCompat.generateViewId() }

    /**
     * 与 [SysUtil.fixOrientation] 共同使用
     */
    @JvmStatic
    fun superSetRequestedOrientation(activity: Activity?, requestedOrientation: Int) {
        if( Build.VERSION.SDK_INT == Build.VERSION_CODES.O &&
            isTranslucentOrFloating( activity ) ) return
        activity?.requestedOrientation = requestedOrientation
    }

    @JvmStatic
    fun isTranslucentOrFloating(activity: Activity?): Boolean {
        if( activity == null ) return false
        var result = false
        try {
            @SuppressLint("PrivateApi")
            val styleable = ClassUtils.getFieldValue(
                Class.forName("com.android.internal.R\$styleable"),
                null,
                "Window"
            ) as IntArray
            val ta = activity.obtainStyledAttributes( styleable )
            ClassUtils.getMethod(
                ActivityInfo::class.java, "isTranslucentOrFloating"
            ).apply {
                isAccessible = true
                result = ObjUtils.parseBoolean( invoke( null, ta ) )
                isAccessible = false
            }
            LogUtil.d(
                "TAG", "SwipeBack.isTranslucentOrFloating -> %s, styleable:%s, ta:%s",
                result, styleable, ta
            )
        } catch (e: Exception) {
            e.printStackTrace()
            LogUtil.e("TAG", "SwipeBack.isTranslucentOrFloating -> e:" + e.message)
        }
        return result
    }

    /**
     * 解决 Android O 透明Activity时会闪退的问题
     */
    @JvmStatic
    fun fixOrientation(activity: Activity?) {
        if( activity == null || Build.VERSION.SDK_INT != Build.VERSION_CODES.O ) return
        try {
            var activityInfo: ActivityInfo? = null
            ClassUtils.getField(
                Activity::class.java, "mActivityInfo"
            )?.let {
                it.isAccessible = true
                activityInfo = it[ this ] as ActivityInfo
                it.isAccessible = false
            }
            activityInfo?.apply {
                screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
                return
            }
            //设置屏幕不固定
            activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    @JvmStatic
    fun getActivity(context: Context?): Activity? {
        var ctx: Context? = context ?: return null
        while ( ctx is ContextWrapper && ctx !is Activity ) {
            ctx = ctx.baseContext
        }
        return if ( ctx is Activity ) ctx else null
    }

    @JvmStatic
    fun checkContextValid(context: Context?): Boolean {
        val ctx = getActivity( context )
        return if ( ctx is Activity ) checkActivityValid( ctx as Activity? ) else true
    }

    @JvmStatic
    fun checkActivityValid(activity: Activity?): Boolean {
        try {
            val act = activity?: return false
            if ( act.isFinishing ) {
                LogUtil.e( "SysUtil", "checkActivityValid -> activity is finishing." )
                return false
            }
            if ( act.isDestroyed ) {
                LogUtil.e( "SysUtil", "checkActivityValid -> activity is destroyed." )
                return false
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return true
    }
}